/*
    bio_molecular.h: Molecular Biology ++ Header file.
    Copyright (C) 2009, 2010, 2011, 2012 Daniel Gutson, Martin Gioiosa,
    Lucas Paradisi, Leonardo Boquillon and Hugo Arregui, FuDePAN

    This file is part of Biopp.

    Aso is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Biopp is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Biopp.  If not, see <http://www.gnu.org/licenses/>.

    NOTE: This file is in prototype stage, and is under active development.
*/

#ifndef BIO_MOLECULAR_H
#define BIO_MOLECULAR_H

#include <map>
#include <mili/mili.h>
#include <stl-debug/stl_debug.h>
#include "biopp/bio_molecular/exceptions.h"
#include "biopp/bio_molecular/definitions.h"
#include "biopp/bio_molecular/triplets.h"

namespace biopp
{

using std::string;
using mili::insert_into;

class NucSequence;

class AminoSequence : public std::vector<Aminoacid>
{
public:
    std::string getString() const
    {
        std::string sequenceString;
        std::vector<Aminoacid>::const_iterator it;
        for (it = begin(); it != end(); ++it)
        {
            sequenceString += it->as_char();
        }
        return sequenceString;
    }
};

template<class TripletClass>
class SequenceBase
{
public:
    SequenceBase(const SequenceBase& other)
        : offset(other.offset),
          seq(other.seq)
    {}

    explicit SequenceBase(const std::string&);

    inline SequenceBase& operator=(const SequenceBase& other);

    inline bool operator==(const SequenceBase& other) const;

    inline bool operator!=(const SequenceBase& other) const;

    inline SequenceBase& operator+=(typename TripletClass::NucType nucl);

    inline typename TripletClass::NucType operator[](size_t idx) const;

    inline typename TripletClass::NucType& operator[](size_t idx);

    friend std::ostream& operator<<(std::ostream& os, const SequenceBase& sequence)
    {
        os << sequence.getString();
        return os;
    }

    inline void clear();

    // Offset refers to the open reading frame (ORF)
    // Its value must be between 0 and 2
    inline void set_offset(size_t new_offset);

    inline size_t get_offset() const;

    inline size_t length() const;

    inline size_t triplets_length() const;

    inline TripletClass get_triplet(size_t i) const throw(OutOfRange);

    template<class TripletType>
    void set_triplet(size_t i, const TripletType& t) throw(OutOfRange)
    {
        if (i >= triplets_length())
            throw OutOfRange();

        const size_t tripletPosition = calcTripletPosition(i);

        seq[tripletPosition] = t[0];
        seq[tripletPosition + 1] = t[1];
        seq[tripletPosition + 2] = t[2];
    }

    inline void set_triplet(size_t i, const TripletClass& t) throw(OutOfRange);

    virtual std::string getString() const
    {
        std::string sequenceString;

        typename std::vector<typename TripletClass::NucType>::const_iterator it;

        for (it = seq.begin() + offset; it != seq.end(); ++it)
        {
            sequenceString.append(1, it->as_char());
        }

        return sequenceString;
    }

    void addNucleotide(const typename TripletClass::NucType& nuc)
    {
        insert_into(seq, nuc);
    }

    inline void complement(SequenceBase<TripletClass>&) const;

    inline void reverseComplement(SequenceBase<TripletClass>&) const;

    inline void complement();

    inline void reverseComplement();


protected:
    size_t offset;
    std::vector<typename TripletClass::NucType> seq;

    size_t calcTripletPosition(size_t i) const;

    SequenceBase()
        : offset(0)
    {}

};

class PseudoNucSequence : public SequenceBase<PseudoTriplet>
{
public:
    PseudoNucSequence()
        : SequenceBase<PseudoTriplet>()
    {}

    PseudoNucSequence(const PseudoNucSequence& other)
        : SequenceBase<PseudoTriplet>(other)
    {}

    explicit PseudoNucSequence(const std::string& str)
        : SequenceBase<PseudoTriplet>(str)
    {}

    PseudoNucSequence(const NucSequence& nuc)
    {
        *this = nuc;
    }

    using SequenceBase<PseudoTriplet>::set_triplet;

    PseudoNucSequence& operator=(const PseudoNucSequence& other)
    {
        SequenceBase<PseudoTriplet>::operator=(other);
        return *this;
    }

    PseudoNucSequence& operator=(const NucSequence&);

    void set_triplet(size_t i, const Triplet& t) throw(OutOfRange);

    void set_triplet(size_t i, const PseudoTriplet& t, PseudoNucSequence& other) const throw(OutOfRange)
    {
        other = *this;
        other.set_triplet(i, t);
    }

    bool isPureSequence() const;

    inline void getNucSequence(NucSequence& n) const;

};

class NucSequence : public SequenceBase<Triplet>
{
public:
    friend PseudoNucSequence& PseudoNucSequence::operator=(const NucSequence&);
    NucSequence()
        : SequenceBase<Triplet>()
    {}

    NucSequence(const NucSequence& other)
        : SequenceBase<Triplet>(other)
    {}

    explicit NucSequence(const std::string& str)
        : SequenceBase<Triplet>(str)
    {}

    using SequenceBase<Triplet>::set_triplet;

    void translate(AminoSequence& aminoseq) const;

    NucSequence& operator=(const NucSequence& other)
    {
        SequenceBase<Triplet>::operator=(other);
        return *this;
    }

    void set_triplet(size_t i, const Triplet& t, NucSequence& other) const throw(OutOfRange)
    {
        other = *this;
        other.set_triplet(i, t);
    }

};

inline void pseudoNucSequence_to_nucSequence(const PseudoNucSequence& pnuc, NucSequence& nuc)
{
    const size_t size = pnuc.length();

    nuc.clear();
    for (size_t i = 0; i < size ; i++)
    {
        nuc.addNucleotide(pnuc[i]);
    }
}

}

#define BIO_MOLECULAR_INLINE_H
#include "bio_molecular_inline.h"
#undef BIO_MOLECULAR_INLINE_H
#endif
